#Class for app config
=begin
Copyright (C) 2014 Enrique Rando

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

http://www.gnu.org/licenses/gpl-2.0.html
=end



class My_configuration
	# Object creation
	# Parameter definitions: array of hashes
	#		:name => name of the parameter. Only letters, digits and underscore (_) are supported.
	#		:kind => :simple (one value), :multiple (more than one value - array), :pairs (array of hashes). Default: :simple
	#		:mandatory => true or false. Default: false
	#		:type => :string, :integer, :boolean. Default: :string
	#		:default => default value
	#		:allowed => value or array of values allowed for the option whose type is different from the option one
	def initialize(definitions)
		# Store definitions
		@definitions = {}
		
		# Option values
		@values = {}
		
		# Check options and set definitions
		definitions.each do |option|
			name = option[:name].to_sym
			
			raise "Duplicated option: #{option[:name]}" if @definitions.has_key?(name)
			@definitions[name] = option
			
			# Set default values
			@definitions[name][:kind] = :simple if not option.has_key?(:kind)
			@definitions[name][:type] = :string if not option.has_key?(:type)
			@definitions[name][:mandatory] = false if not option.has_key?(:mandatory)
			@definitions[name][:allowed] = [] if not option.has_key?(:allowed)
			@definitions[name][:allowed] = [@definitions[name][:allowed]] if not @definitions[name][:allowed].is_a?(Array)
			
			# Check that data is ok
			raise "Invalid character in option name: #{option[:name]}" if option[:name].match(/\W/)
			raise "Invalid kind of option: #{@definitions[name][:kind]}" if not [:simple, :multiple, :pairs].include?(@definitions[name][:kind])
			raise "Invalid option type: #{@definitions[name][:type]}" if not [:string, :integer, :boolean, :symbol, :regexp].include?(@definitions[name][:type])
			
			# Set default values
			if @definitions[name][:kind] == :simple
				self[name] = option[:default] if option.has_key?(:default)
			else
				@values[name] = []
			end
		end
		
		
		# Create regexps for values
		@simple_values = Regexp.new(
				'^(?<param>' + 
				definitions.select{|x| x[:kind] == :simple}.collect{|x| x[:name]}.join("|") + 
				')=(?<value>.*)$'
		)
		
		@multiple_values = Regexp.new(
				'^(?<param>' + 
				definitions.select{|x| x[:kind] == :multiple}.collect{|x| x[:name]}.join("|") + 
				')=(?<value>.*)$'
		)
		
		@pair_values = Regexp.new(
				'^(?<param>' + 
				definitions.select{|x| x[:kind] == :pairs}.collect{|x| x[:name]}.join("|") + 
				')=(?<separator>.)(?<v1>.+)\k<separator>(?<v2>.+)$'
		)
	end

	# Load configuration from a file
	# Parameter: file
	def read(file)
		File.read(file).lines.each_with_index do |x, i|
			line = clean(x)
			
			# Skip blank lines and comments
			next if line.match(/^\s*$/) or line.match(/^\s*#/)
			
			# Process simple value options
			if match = line.match(@simple_values)
				param = match[:param]
				value = clean(match[:value])
				self[param] = value if value != ""
				next
			end
			
			# Process multiple value options
			if match = line.match(@multiple_values)
				param = match[:param]
				value = clean(match[:value])
				self[param] = value if value != ""
				next
			end
			
			# Process pair value options
			if match = line.match(@pair_values)
				param = match[:param]
				v1 = clean(match[:v1])
				v2 = clean(match[:v2])
				self[param] = {v1=>v2} if (v1 != "" and v2 != "")
				next
			end	
			
			raise "Syntax error in '#{file}' line #{i}: #{line.inspect}"
		end
	end
	
	# Sets the value for an option
	# Parameters
	#	option_to_assign
	#	value
	def []=(option_to_assign, value)
		option = option_to_assign.to_sym
		raise "Unknown option: #{option}" if not @definitions.has_key?(option)

		if @definitions[option][:allowed].include?(value) or @definitions[option][:kind]==:pairs
			my_value = value
		else
			my_value = case @definitions[option][:type]
				when :string
					value.to_s
				when :integer
					value.to_i
				when :boolean
					["y", "t", "true", "yes"].include?(value.to_s.downcase)
				when :symbol
					value.to_sym
				when :regexp
					Regexp.new("^" + value.to_s + "$")
				else
					raise "Invalid kind for #{option}: #{@definitions[option][:kind]}"
			end
		end

		if @definitions[option][:kind] == :simple
			@values[option] = my_value
		else
			@values[option] << my_value
		end
	end

	# Gets the value for an option
	# Parameter: option
	def [](option)
		raise "Unknown option: #{option}" if not @definitions.has_key?(option)
		raise "Mandatory option without value: #{option}" if @definitions[option][:mandatory] and not @values.has_key?(option) 

		return @values[option.to_sym]
	end
	
	# Converts the object in a string
	def to_s
		@values.collect{|x, y| "#{x}: #{y.inspect}"}.join("\n")
	end

private
	# Cleans a string
	# Parameter: s	the string
	def clean(s)
		return s.to_s.gsub(/\r|\n/, "").gsub(/(^\s+)|(\s+$)/, "")
	end
end
